package org.uniview.common.i18n;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.apache.struts.util.MessageResources;
import org.apache.struts.util.MessageResourcesFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.uniview.common.util.Utils;


/**
 * Concrete subclass of Struts <code>MessageResources</code> that 
 * reads message keys and corresponding strings from named property 
 * resources via XML resource file.
 * @author James Gao
 * @version 1.0 2006-12-29
 */
public class XMLMessageResources extends MessageResources {

	/**
	 * 
	 */
	private static final long serialVersionUID = 9104270406203579856L;

	/**
	 * The <code>Log</code> instance for this class.
	 */
	protected static final Logger logger = Logger
			.getLogger(XMLMessageResources.class);

	/**
	 * The set of locale keys for which we have already loaded messages, keyed
	 * by the value calculated in <code>localeKey()</code>.
	 */
	protected HashSet loadedLocales = new LinkedHashSet();

	/**
	 * The cache of messages we have accumulated over time, keyed by the
	 * value calculated in <code>messageKey()</code>.
	 */
	protected HashMap messages = new LinkedHashMap();

	/**
	 * The array of message resource files.
	 */
	protected String[] resourceFiles = new String[0];

	/**
	 * Construct a new XMLMessageResources according to the
	 * specified parameters.
	 * @param factory The MessageResourcesFactory that created us
	 * @param config The configuration parameter for this MessageResources
	 */
	public XMLMessageResources(MessageResourcesFactory factory, String config) {
		super(factory, config);
		logger.info("Initializing, config='" + config + "'");
		initConfig(config);
	}

	/**
	 * Construct a new XMLMessageResources according to the
	 * specified parameters.
	 * @param factory The MessageResourcesFactory that created us
	 * @param config The configuration parameter for this MessageResources
	 * @param returnNull The returnNull property we should initialize with
	 */
	public XMLMessageResources(MessageResourcesFactory factory, String config,
			boolean returnNull) {
		super(factory, config, returnNull);
		logger.info("Initializing, config='" + config + "', returnNull="
				+ returnNull);
		initConfig(config);
	}

	/**
	 * Initializing message resource files with config text. 
	 * @param config configuration.
	 */
	private void initConfig(String config) {
		config = Utils.setString(config);
		resourceFiles = config.split(",");
		for (int i = 0; i < resourceFiles.length; i++) {
			resourceFiles[i] = resourceFiles[i].trim();
		}
	}

	/**
	 * Returns a text message for the specified key, for the default Locale.
	 * A null string result will be returned by this method if no relevant
	 * message resource is found for this key or Locale, if the
	 * <code>returnNull</code> property is set.  Otherwise, an appropriate
	 * error message will be returned.
	 * <p>
	 * This method must be implemented by a concrete subclass.
	 * @param locale The requested message Locale, or <code>null</code>
	 *  for the system default Locale
	 * @param key The message key to look up
	 * @return text message for the specified key and locale
	 */
	public String getMessage(Locale locale, String key) {
		// Initialize variables we will require
		String localeKey = localeKey(locale);
		String originalKey = messageKey(localeKey, key);
		String messageKey = null;
		String message = null;
		int underscore = 0;
		boolean addIt = false; // Add if not found under the original key

		// Loop from specific to general Locales looking for this message
		while (true) {
			// Load this Locale's messages if we have not done so yet
			loadLocale(localeKey);
			// Check if we have this key for the current locale key
			messageKey = messageKey(localeKey, key);
			synchronized (messages) {
				message = (String) messages.get(messageKey);
				if (message != null) {
					if (addIt) {
						messages.put(originalKey, message);
					}
					return (message);
				}
			}

			// Strip trailing modifiers to try a more general locale key
			addIt = true;
			underscore = localeKey.lastIndexOf("_");
			if (underscore < 0) {
				break;
			}
			localeKey = localeKey.substring(0, underscore);
		}

//		// Try the default locale if the current locale is different
//		if (!defaultLocale.equals(locale)) {
//			localeKey = localeKey(defaultLocale);
//			messageKey = messageKey(localeKey, key);
//			loadLocale(localeKey);
//			synchronized (messages) {
//				message = (String) messages.get(messageKey);
//				if (message != null) {
//					messages.put(originalKey, message);
//					return (message);
//				}
//			}
//		}

		// As a last resort, try the default Locale
		localeKey = "";
		messageKey = messageKey(localeKey, key);
		loadLocale(localeKey);
		synchronized (messages) {
			message = (String) messages.get(messageKey);
			if (message != null) {
				messages.put(originalKey, message);
				return (message);
			}
		}

		// Return an appropriate error indication
		if (returnNull) {
			return (null);
		} else {
//			return ("???" + messageKey(locale, key) + "???");
			//if value is not found, return original key.
			return key;
		}

	}

	/**
	 * Load the messages associated with the specified Locale key.
	 * @param localeKey Locale key for the messages to be retrieved.
	 */
	protected synchronized void loadLocale(String localeKey) {
		// Have we already attempted to load messages for this locale?
		if (loadedLocales.contains(localeKey)) {
			return;
		}
		loadedLocales.add(localeKey);

		//According to locale, loading each file
		for (int i = 0; i < resourceFiles.length; i++) {
			loadLocale(localeKey, resourceFiles[i]);
		}
	}

	/**
	 * Load the messages associated with the specified Locale key.  For this
	 * implementation, the <code>config</code> property should contain a fully
	 * qualified package and resource name, separated by periods, of a series
	 * of property resources to be loaded from the class loader that created
	 * this XMLMessageResources instance.
	 * <p>XML File format :<br>
	 *  &lt;resources&gt;<br>
	 *  &nbsp;&nbsp;   &lt;resource key="key_name"&gt;Key value&lt;/resource&gt;<br>
	 *  &lt;/resources&gt;<br></p>
	 * @param localeKey Locale key for the messages to be retrieved.
	 * @param file XML message resource file in the classpath.
	 */
	protected synchronized void loadLocale(String localeKey, String file) {
		// Set up to load the resource for this locale key, if we can
		String name = file.replace('.', '/');
		if (localeKey.length() > 0) {
			name += "_" + localeKey;
		}
		name += ".xml";
		InputStream is = null;
		Properties props = new Properties();
		// Load the specified xml resource
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		if (loader == null) {
			loader = this.getClass().getClassLoader();
		}
		is = loader.getResourceAsStream(name);

		if (is != null) {
			Document doc = new Document();
			try {
				SAXBuilder builder = new SAXBuilder();
				doc = builder.build(is);
				if (doc.hasRootElement()) {
					Element root = doc.getRootElement();
					List children = root.getChildren("resource");
					for (int i = 0; i < children.size(); i++) {
						Element el = (Element) children.get(i);
						String key = el.getAttributeValue("key");
						String message = el.getText();
						if (key != null && key.length() > 0)
							props.setProperty(key, message);
					}
				}
			} catch (IOException e) {
				logger.error("Reading i18n resource file '" + name
						+ "' failed.", e);
			} catch (JDOMException e) {
				logger.error("Parsing i18n resource file '" + name
						+ "' failed.", e);
			} finally {
				try {
					is.close();
				} catch (IOException e) {
					logger.error("Closing input stream of i18n resource file '"
							+ name + "' failed.", e);
				}
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loading i18n resource file '" + name
						+ "' completed.");
			}
		}

		// Copy the corresponding values into our cache
		if (props.size() < 1) {
			return;
		}
		synchronized (messages) {
			for (Iterator i = props.entrySet().iterator(); i.hasNext();) {
				Entry e = (Entry) i.next();
				String key = (String) e.getKey();
				String messageKey = messageKey(localeKey, key);
				//Only load the first message for same message key.
				if (!messages.containsKey(messageKey)) {
					messages.put(messageKey, e.getValue());
				}
			}
		}

	}

}
